package sun.scheduler.core.leveling;

import java.util.*;


import sun.scheduler.core.ScheduleGraph;
import sun.scheduler.core.ScheduleGraph.StartTimes;
import sun.scheduler.core.job.DummyJob;
import sun.scheduler.core.job.Process;
import sun.scheduler.core.job.Job;
import sun.scheduler.core.job.SchedulingInfo;
import sun.scheduler.core.resource.Resource;
import sun.scheduler.core.resource.ResourceProfile;
import sun.scheduler.core.resource.ResourceProfileMap;
import sun.scheduler.core.scheduling.ResourceScheduler;
import sun.scheduler.util.collections.Heap;
import sun.scheduler.util.graph.Edge;
import sun.scheduler.util.graph.Node;

/**
 * The implementation of the leveling algorithm.
 *
 * @author mfj
 */
public class LevelingAlgorithm extends ResourceScheduler
{

    private Job unlevelable;
    private long maxEndTime;
    private Heap<ResourceProfile> unleveledRessources;
    private HashMap<Job, Long> initESTs;
    private Resource priorized = null;

    /**
     * Instantiates a new {@link ResourcesGraphr} from the sepcified {@link sGraph}
     *
     * @param sGraph the sGraph which jobs are to set.
     */
    public LevelingAlgorithm(ScheduleGraph sGraph, ResourceProfileMap rMap, Resource priorized)
    {
        this(sGraph, rMap);
        this.priorized = priorized;
    }

    /**
     * Instantiates a new {@link ResourcesGraphr} from the sepcified {@link sGraph}
     *
     * @param sGraph the sGraph which jobs are to set.
     */
    public LevelingAlgorithm(ScheduleGraph sGraph, ResourceProfileMap rMap)
    {
        super(sGraph, rMap);

        // save initial EST into iEST array where iEST[job.getJobInfoId()] is EST
        // initialize the capacities of the resource profile with
        // min{ ressource | } 
        sGraph.updateJobTimeParameters();
        initESTs = new HashMap<Job, Long>(sGraph.getJobs().size());
        for (Job job : sGraph.getJobs())
        {
            if (job instanceof DummyJob)
                continue;
            SchedulingInfo sInfo = (SchedulingInfo) job.getProcess(CS);
            if (sInfo.isCritical())
            {
                ResourceProfile rProfile = rMap.getResourceProfile(job.resource);
                rProfile.pack(sInfo.getEarliestStartTime(), sInfo.getDuration(), (int) sInfo.getAmountOfResources());
            }

            initESTs.put(job, job.getEarliestStartTime(CS));
        }

        unleveledRessources = new Heap(new RCostComparator());
        for (ResourceProfile rProfile : rMap)
            unleveledRessources.add(rProfile);
    }

    private void increaseCapacity()
    {
        ResourceProfile currProfile = unleveledRessources.poll();
        long finalCapacity = Math.max(currProfile.getMaxCapacity() + 1, currProfile.getResource().maxCapacity);
        currProfile.setMaxCapacity(finalCapacity);
    }

    private void decreaseCapacity()
    {
        ResourceProfile rProfile = unleveledRessources.peek();
        long capacity = rProfile.getMaxCapacity() - 1;
        if (capacity == 0)
            unleveledRessources.poll();
        else
            rProfile.setMaxCapacity(capacity);
    }

    @Override
    public void run()
    {
        ArrayList<Node<StartTimes, Job>> topologicOrder = schGraph.topologicOrder();

        // determine the upper ressource capacities
        while (!unleveledRessources.isEmpty())
        {
            unlevelable = null;
            for (Node<StartTimes, Job> node : topologicOrder)
            {
                if (unlevelable != null)
                {
                    increaseCapacity();
                    if(unleveledRessources.size() > 1)
                        reset();
                    break;
                }

                level(node.getInEdges());
            }

            if (unlevelable == null)
                decreaseCapacity();
        }

        // run scheduling again to comupte the sGraph
        super.run();
    }

    private void reset()
    {
        for (Job job : schGraph.getJobs())
        {
            if (job instanceof DummyJob)
                break;

            job.setEarliestStartTime(CS, initESTs.get(job));
        }

        resProfiles.clear();
    }

    private void level(ArrayList<Edge<StartTimes, Job>> edges)
    {
        Heap<Job> stHeap = new Heap<Job>(edges.size(), comp);
        for (Edge<StartTimes, Job> edge : edges)
        {
            Job curr = edge.getData();
            if (curr instanceof DummyJob)
                continue;

            stHeap.add(curr);
        }

        while (!stHeap.isEmpty())
        {
            Job current = stHeap.poll();

            if (current instanceof DummyJob)
                continue;

            long oLength = schGraph.getTarget().getData().getEarliestStartTime();
            if (!schedule(current) || oLength < schGraph.getTarget().getData().getEarliestStartTime())
                unlevelable = current;
        }
    }

    public class RCostComparator implements Comparator<ResourceProfile>
    {

        @Override
        public int compare(ResourceProfile t, ResourceProfile t1)
        {
            int compFreeCap = (int)(t.getMinAvailableResources()- t1.getMinAvailableResources());
            if (compFreeCap == 0)
            {

                Resource r = t.getResource();
                Resource r1 = t1.getResource();
                if (r == priorized)
                    return -1;
                if (r1 == priorized)
                    return 1;

                return (int) (r1.costPPT - r.costPPT);
            }
            return compFreeCap;
        }

    }
}
